/*
 * ks0108b.c
 *
 *  Created on: Jul 11, 2011
 *      Author: Orlando Arias
 *     License: GPL v3
 *
 *    ks0108b
 *    Copyright (C) 2011  Orlando Arias
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

// TODO: Draft a specification for this before things get out of hand.

#include <util/delay.h>

#include "font3x5.h"
#include "ks0108b.h"
#include "ks_handler.h"

// TODO: Provide better delay loop.
#define _wait_until_free() while( (ks_strobe(1, _cs) & 0x80 ) )
#define _wait_until_free_() _delay_us(25)
#define wait_until_free() wait_until_free_loop()

#ifndef FONT_INCLUDE_
#	error Must include one font file.
#endif


struct _ks_status {
	uint8_t page;
	uint8_t address;
	uint8_t Z;
};

struct _ks_status _status[2];

// TODO: Provide better Chip Select index?
uint8_t _cs;

uint8_t ks_strobe();
void ks_sendRaw(uint8_t data, uint8_t rs);
void wait_until_free_loop();

void ks_init(){
	/*
	 * Datasheet states that a reset turns off the LCD and the
	 * start line becomes 0.
	 */

	DATADDR = 0xff;
	CTRLDDR = 0xff;
	__asm("nop");

	CTRLOUT &= ~(1 << RES);	// reset the LCD
	_delay_ms(1);
	CTRLOUT |= (1 << RES);
	wait_until_free();
	_cs = 1;
	wait_until_free();

	for (uint8_t i = 0; i < 2; i++){
		ks_sendCommand(KS_ON_OFF | 0x01, i);
		ks_sendCommand(KS_SET_Y, i);
		ks_sendCommand(KS_SET_PAGE, i);
	}
}

int ks_writeChar(char c, FILE *unused){
	// TODO: Find a way to deal with special case where
	// (_status[_cs].address % 4) is true

	static uint8_t _last_tab = 0;	// record whether last char
									// was a tab or a space
	if (c == '\n'){
		// newline
		for (uint8_t i = 0; i < 2; i++){
			uint8_t p = _status[i].page;
			ks_sendCommand(KS_SET_PAGE | ((p == 7) ? 0 : p + 1), i);
			ks_sendCommand(KS_SET_Y, i);
		}
		_cs = 0;

	} else if (c == '\r') {
		// carriage return
		ks_sendCommand(KS_SET_Y, 0);

	} else if (c == '\t') {
		// tab
		uint8_t t = TAB_CONST -
				((_status[_cs].address/(FONT_W+1)) % TAB_CONST);
		t %= TAB_CONST;
		if (_last_tab) t += TAB_CONST;
		for(uint8_t i = 0; i < t; i++)
			ks_writeChar(' ', 0);

	} else {
		// deal with the rest of the characters
		if (c == ' ') {
			// space
			for(uint8_t i = 0; i < FONT_W + 1; i++)
					ks_sendData(0x00, _cs);

		} else if (c >= '!' && c <= 'z' + 8){
			// this is a standard character read from program memory
			for(uint8_t i = 0; i < FONT_W; i++)
				ks_sendData(pgm_read_byte(&font[3*(c -'!') + i]), _cs);
			ks_sendData(0, _cs);

		} else {
			// no valid character, print a box
			for(uint8_t i = 0; i < FONT_W; i++)
				ks_sendData((i % 2) ? 0x44 : 0x7c, _cs);
			ks_sendData(0x00, _cs);
		} // standard characters

		// checks for end of page
		if (_status[_cs].address == 64){
			if (_cs == 1){
				// end of page on C1 is equivalent to a newline
				ks_writeChar('\n', 0);
			} else {
				// end of page on C0 sets current page to C1 and sets
				// up address at 0 to ensure continuity.
				ks_sendCommand(KS_SET_PAGE | _status[0].page, 1);
				ks_sendCommand(KS_SET_Y, 1);
			}
		} // EOL

	} // CHARACTER SELECT
	_last_tab = (c == '\t' || c == ' ');

	return 0;
}

void ks_sendCommand(ks_command c, uint8_t cs){
	_cs = cs;
	ks_sendRaw(c, 0);
	// status update
	if ((c & KS_SET_Y) == KS_SET_Y)
		_status[cs].address = c & (~KS_SET_Y);
	else if ((c & KS_SET_PAGE) == KS_SET_PAGE)
		_status[cs].page = c & (~KS_SET_PAGE);
	else if ((c & KS_SET_Z) == KS_SET_Z)
		_status[cs].Z = c & (~KS_SET_Z);
}

void ks_sendData(uint8_t byte, uint8_t cs){
	_cs = cs;
	ks_sendRaw(byte, 1);
	if (_status[cs].address == 64)
		_status[cs].address = 0;
	else
		_status[cs].address++;
}

uint8_t ks_strobe(){
	// TODO: Optimize
	uint8_t t = 0;
	CTRLOUT &= ~(_BV(CS1) | _BV(CS2));	// deselect both chips
	CTRLOUT |= (1 << _cs);

	_delay_us(.5);			// Twl = 450ns min
	CTRLOUT |= _BV(EN);
	_delay_us(.5);			// Twh = 450ns min

	CTRLOUT &= ~_BV(EN);
	return t;
}

void wait_until_free_loop(){
	CTRLOUT &= ~(_BV(CS1) | _BV(CS2) | _BV(RS));
	CTRLOUT |= ((1 << _cs) | _BV(RW));

	DATADDR = 0x00;
	__asm("nop");

	//uint8_t t;
	do{
		_delay_us(.5);
		CTRLOUT |= _BV(EN);
		_delay_us(.5);
		CTRLOUT &= ~_BV(EN);
		// t = DATAIN;
	} while(DATAIN & 0x90);
	DATADDR = 0xff;
}

void ks_sendRaw(uint8_t data, uint8_t rs){
	// TODO: Optimize
	if (rs)
		CTRLOUT |= _BV(RS);
	else
		CTRLOUT &= ~_BV(RS);

	CTRLOUT &= ~_BV(RW);
	DATAOUT = data;

	ks_strobe();

	wait_until_free();
}
